package utils

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"duoke/model"
	"duoke/model/yogaModel"
	"duoke/utils/api345"
	"duoke/utils/gconv"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gogf/gf/v2/os/gtime"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var Common = new(commonUtils)

type commonUtils struct{}

// 获取客户端ip
func (u *commonUtils) GetClientIP(c *gin.Context) string {
	// 尝试从 X-Forwarded-For 头获取客户端 IP
	xffHeader := c.Request.Header.Get("X-Forwarded-For")
	if xffHeader != "" {
		return xffHeader
	}
	// 尝试从 X-Real-IP 头获取客户端 IP
	realIP := c.Request.Header.Get("X-Real-IP")
	if realIP != "" {
		return realIP
	}
	// 如果以上头都不存在，从 RemoteAddr 获取客户端 IP
	return c.ClientIP()
}

// 获取用户ip
func (u *commonUtils) GetIp() (*model.IpAddr, error) {
	baseURL := "http://ip-api.com/json"
	params := url.Values{}
	send, err := api345.NewConfig().Send(params, baseURL)
	if err != nil {
		return nil, err
	}
	var respone model.IpAddr
	err = json.Unmarshal(send, &respone)
	if err != nil {
		return nil, err
	}
	return &respone, nil
}

// PKCS7Padding 填充数据
func (u *commonUtils) PKCS7Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padtext...)
}

// PKCS7UnPadding 去除填充数据
func (u *commonUtils) PKCS7UnPadding(data []byte) []byte {
	length := len(data)
	unpadding := int(data[length-1])
	return data[:(length - unpadding)]
}

// AESEncrypt AES CBC模式加密
func (u *commonUtils) AESEncrypt(plaintext []byte, key []byte, iv []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	blockSize := block.BlockSize()
	plaintext = u.PKCS7Padding(plaintext, blockSize)
	blockMode := cipher.NewCBCEncrypter(block, iv)
	ciphertext := make([]byte, len(plaintext))
	blockMode.CryptBlocks(ciphertext, plaintext)
	encryptedBase64 := base64.StdEncoding.EncodeToString(ciphertext)
	return encryptedBase64, nil
}

// AESDecrypt AES CBC模式解密
func (u *commonUtils) AESDecrypt(ciphertextBase64 string, key []byte, iv []byte) (string, error) {
	// 验证密钥长度（16/24/32字节对应AES-128/192/256）
	keyLen := len(key)
	if keyLen != 16 && keyLen != 24 && keyLen != 32 {
		return "", errors.New("密钥长度必须是16/24/32字节，当前长度: %d\", keyLen")
	}

	// 验证IV长度（必须为16字节）
	if len(iv) != aes.BlockSize {
		return "", fmt.Errorf("IV长度必须是16字节，当前长度: %d", len(iv))
	}

	// 解码Base64密文
	ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
	if err != nil {
		return "", fmt.Errorf("Base64解码失败: %v", err)
	}

	// 验证密文长度（必须是块大小的倍数）
	if len(ciphertext)%aes.BlockSize != 0 {
		return "", fmt.Errorf("密文长度必须是16字节的倍数，当前长度: %d", len(ciphertext))
	}

	// 创建AES解密器
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建AES解密器失败: %v", err)
	}

	// 创建CBC模式解密器
	mode := cipher.NewCBCDecrypter(block, iv)

	// 解密数据
	plaintext := make([]byte, len(ciphertext))
	mode.CryptBlocks(plaintext, ciphertext)

	// 去除PKCS#7填充
	plaintext = u.PKCS7UnPadding(plaintext)
	return string(plaintext), nil
}

// 获取视频时长
func (u *commonUtils) GetVideoDuration(videoAddr string) (*model.VideoInfo, error) {
	baseURL := videoAddr + "?avinfo"
	params := url.Values{}
	send, err := api345.NewConfig().Send(params, baseURL)
	if err != nil {
		return nil, err
	}
	var respone model.VideoInfo
	err = json.Unmarshal(send, &respone)
	if err != nil {
		return nil, err
	}
	return &respone, nil
}

// ArrayToString 数组转string
func (u *commonUtils) ArrayToString(arr interface{}) string {
	value := reflect.ValueOf(arr)
	// 检查输入是否为数组或切片
	if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
		return ""
	}
	var strs []string
	for i := 0; i < value.Len(); i++ {
		element := value.Index(i)
		switch element.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			strs = append(strs, strconv.FormatInt(element.Int(), 10))
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			strs = append(strs, strconv.FormatUint(element.Uint(), 10))
		case reflect.Float32, reflect.Float64:
			strs = append(strs, strconv.FormatFloat(element.Float(), 'f', -1, 64))
		case reflect.String:
			strs = append(strs, element.String())
		default:
			strs = append(strs, fmt.Sprintf("%v", element.Interface()))
		}
	}
	return strings.Join(strs, ",")
}

// GetOrder 获取订单号
func (u *commonUtils) GetOrder() string {
	now := time.Now()
	// 定义时间格式
	format := "20060102"
	// 将当前时间按照指定格式进行格式化
	formattedTime := now.Format(format)
	// 生产订单号
	orderNo := gconv.String(formattedTime) + gconv.String(time.Now().Local().Unix())
	return orderNo
}

// 读取文件url后缀

func (u *commonUtils) GetURLSuffix(url string) string {
	lastIndex := strings.LastIndex(url, ".")
	if lastIndex == -1 {
		return ""
	}
	return url[lastIndex+1:]
}

// PostCurl post上传方法
func (u *commonUtils) PostCurl(jsonData []byte, url string, header ...map[string]interface{}) ([]byte, error) {
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")

	// 设置自定义请求头
	if len(header) > 0 && header[0] != nil {
		for key, value := range header[0] {
			req.Header.Set(key, gconv.String(value))
		}
	}

	// 创建HTTP客户端并发送请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// 读取响应体
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

// 构造体转map
func (u *commonUtils) StructToMap(obj interface{}) (map[string]interface{}, error) {
	var result map[string]interface{}
	// 先将结构体序列化为 JSON 字节切片
	data, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	// 再将 JSON 字节切片反序列化为 map
	err = json.Unmarshal(data, &result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// IsTimeInRange
//
// 断当前时间是否在指定的时间范围内（不包含日期）
func (u *commonUtils) IsTimeInRange(startTimeStr, endTimeStr string) (bool, error) {
	// 获取当前时间
	now := time.Now()

	// 解析开始时间和结束时间
	layout := "15:04:05" // 时间格式：小时:分钟:秒
	start, err := time.Parse(layout, startTimeStr)
	if err != nil {
		return false, fmt.Errorf("解析开始时间失败: %v", err)
	}

	end, err := time.Parse(layout, endTimeStr)
	if err != nil {
		return false, fmt.Errorf("解析结束时间失败: %v", err)
	}

	// 提取当前时间的时、分、秒
	nowHour, nowMinute, nowSecond := now.Clock()
	nowTime := time.Date(0, 0, 0, nowHour, nowMinute, nowSecond, 0, time.UTC)

	// 提取开始时间和结束时间的时、分、秒
	startHour, startMinute, startSecond := start.Clock()
	startTime := time.Date(0, 0, 0, startHour, startMinute, startSecond, 0, time.UTC)

	endHour, endMinute, endSecond := end.Clock()
	endTime := time.Date(0, 0, 0, endHour, endMinute, endSecond, 0, time.UTC)

	// 判断当前时间是否在范围内
	// 如果开始时间小于结束时间，直接比较
	if startTime.Before(endTime) {
		return !nowTime.Before(startTime) && !nowTime.After(endTime), nil
	}

	// 如果开始时间大于结束时间（跨越午夜），则判断是否在开始时间之后或者在结束时间之前
	return !nowTime.Before(startTime) || !nowTime.After(endTime), nil
}

// IsInTimeSlot 判断某个时间是否落在某个时间段内
func (u *commonUtils) IsInTimeSlot(t, start, end string) bool {
	layout := "15:04"
	tm, _ := time.Parse(layout, t)
	s, _ := time.Parse(layout, start)
	e, _ := time.Parse(layout, end)

	return tm.After(s) && tm.Before(e)
}

// 获取所属时间段
func (u *commonUtils) GetBelongingSlot(timeSlots *[2]yogaModel.TimeSlot) string {
	now := time.Now()
	currentTime := now.Format("15:04") // 获取 HH:mm 格式的时间
	var str string
	if timeSlots[1].StartTime != "" {
		str = timeSlots[1].StartTime + "-" + timeSlots[1].EndTime
	} else {
		if timeSlots[0].StartTime != "" {
			str = timeSlots[0].StartTime + "-" + timeSlots[0].EndTime
		}
	}
	for _, slot := range timeSlots {
		if u.IsInTimeSlot(currentTime, slot.StartTime, slot.EndTime) {
			str = slot.StartTime + "-" + slot.EndTime
		}
	}
	return str
}

// ClassifyDateString 输入是 YYYY-MM-DD 格式的字符串，判断是今天/明天/其他+星期几
func (u *commonUtils) ClassifyDateString(dateStr string) (string, error) {
	// 中文星期映射
	var weekDayMap = map[time.Weekday]string{
		time.Sunday:    "星期日",
		time.Monday:    "星期一",
		time.Tuesday:   "星期二",
		time.Wednesday: "星期三",
		time.Thursday:  "星期四",
		time.Friday:    "星期五",
		time.Saturday:  "星期六",
	}
	// 解析输入的日期字符串
	targetDate, err := time.Parse("20060102", dateStr)
	if err != nil {
		return "", err
	}

	now := time.Now()
	today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
	tomorrow := today.Add(24 * time.Hour)
	target := time.Date(targetDate.Year(), targetDate.Month(), targetDate.Day(), 0, 0, 0, 0, now.Location())

	// 判断是否是今天、明天 或 其他日期
	switch {
	case target.Equal(today):
		return "今天", nil
	case target.Equal(tomorrow):
		return "明天", nil
	default:
		weekDay := weekDayMap[target.Weekday()]
		return fmt.Sprintf("%s %s", target.Format("01-02"), weekDay), nil
	}
}

// 获取今天是周几
func (u *commonUtils) GetWeekDays(weekday time.Weekday) int {
	weekdays := map[time.Weekday]int{
		time.Sunday:    7,
		time.Monday:    1,
		time.Tuesday:   2,
		time.Wednesday: 3,
		time.Thursday:  4,
		time.Friday:    5,
		time.Saturday:  6,
	}
	return weekdays[weekday]
}

// 通过日期获取周几
func (u *commonUtils) Day2Week(date string) int {
	// 解析日期字符串为 gtime.Time 对象
	t := gtime.NewFromStr(date)

	// 获取星期几（time.Weekday 类型）
	weekday := t.Weekday()

	// 将 Weekday 转换为数字（Monday = 0, Sunday = 6）
	dayNumber := int(weekday)

	// 如果你希望周一返回 1，周日返回 7（符合中国习惯），可以这样处理：
	if dayNumber == 0 {
		dayNumber = 7 // 周日是第7天
	} else {
		dayNumber = dayNumber // 周一到周六保持原样（1-6）
	}

	return dayNumber
}

// TimeAfter 两个时间进行比较
func (u *commonUtils) TimeAfter(timeStr1, timeStr2 string) int {
	t1, err := gtime.StrToTime(timeStr1)
	if err != nil {
		panic(err)
	}
	t2, err := gtime.StrToTime(timeStr2)
	if err != nil {
		panic(err)
	}
	if t1.After(t2) {
		return 1 // t1 大于 t2
	} else if t1.Before(t2) {
		return 2 // t2 大于 t1
	} else {
		return 3 // 相等
	}
}
